#
# ------------------------------------------------------------------------------
# Scale Audio (scale)
#
# license:       GNU General Public License v3.0
#                Copyright (c) 2014 Jasper R. Danielson
#                See LICENSE.txt for more information
# website:       http://scaleaudio.org
# version:       0.1.1
#
# ------------------------------------------------------------------------------
#
# file:          Sound.py
# usage:         scale.Sound()
# purpose:       
# classes:       Sound
# functions:     record
#                play
#                info
# constants:     tmpdir:   The location of the temporary folder where temporary
#                          audio files are stored.
#                soxdir:   The location of the SoX executable.
#                scaledir: The location of the scale source folder.
#

from __future__ import division, print_function

import platform
import os
import inspect
import shutil
import subprocess
import shlex
import math

#---Set temp folder location---#
scaledir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
tmpdir = os.path.expanduser("~") + "/.scale"

#---Check that user has valid permissions for tmp folder---#
if not os.access(os.path.expanduser("~"),os.W_OK):
    raise SystemError("User does not have valid permissions to write to "+os.path.expanduser("~")+".")

#---Clear contents of temp folder---#
if os.path.exists(tmpdir):
    shutil.rmtree(tmpdir) #Delete tmp folder
os.mkdir(tmpdir) #Create new, empty tmp folder

#---Find SoX executable, if installed---#
if platform.system() == "Windows":
    soxdir = scaledir + "/sox/Windows/sox.exe"
elif platform.system() == "Darwin":
    soxdir = scaledir + "/sox/Mac/sox"
else:
    if os.path.exists(scaledir + "/sox/sox/bin/sox"):
        soxdir = scaledir + "/sox/sox/bin/sox"
    elif os.path.exists('/usr/bin/sox'):
        soxdir = "sox"
    elif os.path.exists('/usr/local/bin/sox'):
        soxdir = "sox"
    else:
        raise ImportWarning('SoX not properly installed on this computer.')

# ------------------------------------------------------------------------------

class Sound():
    '''Create a new Sound object.
    
    Parameters
    ----------
    path : str, optional
        Path to an existing audio file. If specified, the Sound object will open
        that audio file during creation. If not specified, the Sound object will
        remain empty.
    sample_rate : int, optional
        The desired sample rate for the audio, in samples per second (Hz). Common 
        sample rates are: 32000, 44100 (CD), 48000, 88200, 96000, and 192000 Hz.
    channels : int, optional
        The number of channels used in the audio. One (mono) and two (stereo) are 
        widely used. Surround sound audio typically contains six or more channels.
    bits : int, optional
        The number of bits used to store each sample. Today, 16-bit is commonly
        used. 8-bit was popular in the early days of computer audio. 24-bit is used
        in the professional audio arena. Other sizes are also used.
    
    Returns
    -------
    out : Sound
        A Sound object satisfying the specified requirements.
    '''
    def __init__(self,path="",sample_rate=44100,channels=2,bits=16):
        #---Check inputs---#
        check_num(sample_rate,"sample_rate")
        check_num(channels,"channels")
        check_num(bits,"bits")
        #---Set Sound attributes---#
        self.sample_rate = sample_rate
        self.channels = channels
        self.bits = bits
        #---Find valid file location---#
        npath = unused()
        self.location = npath
        #---Create empty audio file at self.location---#
        command = '"%s" -n -r %s -c %s -b %s "%s" trim 0 0'%(soxdir,self.sample_rate,self.channels,self.bits,self.location)
        run_command(command)
        #---Open audio file (if given)---#
        if path != "":
            self.open(path)
    
    def open(self,path):
        '''Opens and concatenates the specified audio file.
        
        Parameters
        ----------
        path : str
            Path to an existing audio file.'''
        #---Check and reformat input path---#
        #Expands `path' to absolute form
        #Checks that `path' refers to a valid file
        path = reduce_path(path,file=True)
        #---Find valid new file location---#
        npath = unused()
        #---Open audio file---#
        command = '"%s" --ignore-length "%s" -r %s -c %s -b %s -t wav - | "%s" "%s" -t wav - "%s"' % (soxdir,path,self.sample_rate,self.channels,self.bits,soxdir,self.location,npath)
        run_command(command)
        self.location = npath
    
    def save(self,path):
        '''Save the Sound object to the specified location.
        
        Parameters
        ----------
        path : str
            Desired path to save the Sound object's audio. The path should specify a
            file in a valid directory.'''
        #---Check and reformat input path---#
        #Expands `path' to absolute form
        #Checks that `path' is placed in a valid directory
        path = reduce_path(path,folder=True)
        #---Save audio file---#
        command = '"%s" "%s" "%s"' % (soxdir,self.location,path)
        run_command(command,False) #Allows Python code to continue running while
                                   #OS saves the audio file.
    
    def record(self,duration):
        '''Record a sample of audio from the system default audio input.
        
        Parameters
        ----------
        duration : int, float
            The desired length of time (in seconds) to record audio.'''
        #---Check input duration---#
        check_num(duration,"duration",0)
        #---Find valid new file location---#
        npath = unused()
        #---Record audio---#
        command = '"%s" -d -t wav -r %s -c %s -b %s "%s" trim 0 %s' % (soxdir,self.sample_rate,self.channels,self.bits,npath,duration)
        run_command(command)
        self.open(npath) #Only open file once it's finished being written to.
    
    def play(self):
        '''Play the Sound object to the system default audio player.'''
        command = '"%s" "%s" -d' % (soxdir,self.location)
        run_command(command,False)
    
    def clear(self):
        '''Clears the Sound object.'''
        #---Create blank audio file---#
        ret = Sound("",self.sample_rate,self.channels,self.bits)
        #---Point current Sound object to new blank audio---#
        self.location = ret.location
    
    def concatenate(self,sound):
        '''Concatenates the given Sound object onto the end of the current Sound
        object.
        
        x.concatenate(y) <==> x = x + y
        
        Parameters
        ----------
        sound : Sound
            A Sound object.'''
        #---Check input sound---#
        if not isinstance(sound,Sound):
            raise TypeError("`sound' parameter must specify scale.Sound object")
        #---Find valid new file location---#
        npath = unused()
        #---Concatenate the new sound file to the old one---#
        command = '"%s" "%s" "%s" -r %s -c %s -b %s "%s"'%(soxdir,self.location,sound.location,self.sample_rate,self.channels,self.bits,npath)
        run_command(command)
        self.location = npath
    
    def get_duration(self):
        '''Returns the duration of the Sound object, in seconds.'''
        #---Get info about sound file---#
        command = '"%s" --i -d "%s"'%(soxdir,self.location)
        try:
            ret = subprocess.check_output(command)
        except:
            ret = subprocess.check_output(shlex.split(command))
        #---Extract and reformat duration data---#
        ret = ret[:-2]
        ret = ret.split(":")
        ret = int(ret[0])*3600 + int(ret[1])*60 + float(ret[2])
        return ret
    
    def get_channels(self):
        '''Returns the number of audio channels of the Sound object.'''
        return self.channels
    
    def get_bits(self):
        '''Returns the bit width of the Sound object.'''
        return self.bits
    
    def get_sample_rate(self):
        '''Returns the sample rate of the Sound object, in Hertz.'''
        return self.sample_rate
    
    def set_channels(self,channels):
        '''Sets the number of audio channels.
        
        Parameters
        ----------
        channels : int
            The number of channels used in the audio. One (mono) and two (stereo) are
            widely used. Surround sound audio typically contains six or more channels.'''
        #---Check and format input channels---#
        channels = int(channels)
        check_num(channels,"channels",0)
        #---Create new Sound with updated attributes---#
        self.channels = channels
        ret = Sound("",self.sample_rate,self.channels,self.bits)
        ret.open(self.location)
        #---Update current Sound---#
        self.location = ret.location
    
    def set_bits(self,bits):
        '''Sets the audio bit width.
        
        Parameters
        ----------
        bits : int
            The number of bits used to store each sample. Today, 16-bit is commonly
            used. 8-bit was popular in the early days of computer audio. 24-bit is used
            in the professional audio arena. Other sizes are also used.'''
        #---Check and format input bits---#
        bits = int(bits)
        check_num(bits,"bits",0)
        #---Create new Sound with updated attributes---#
        self.bits = bits
        ret = Sound("",self.sample_rate,self.channels,self.bits)
        ret.open(self.location)
        #---Update current Sound---#
        self.location = ret.location

    def set_sample_rate(self,sample_rate):
        '''Sets the audio sample rate.
        
        Parameters
        ----------
        sample_rate : int
            The desired sample rate for the audio, in samples per second (Hz). Common
            sample rates are: 32000, 44100 (CD), 48000, 88200, 96000, and 192000 Hz.'''
        #---Check and format input sample_rate---#
        sample_rate = int(sample_rate)
        check_num(sample_rate,"sample_rate",0)
        #---Create new Sound with updated attributes---#
        self.sample_rate = sample_rate
        ret = Sound("",self.sample_rate,self.channels,self.bits)
        ret.open(self.location)
        #---Update current Sound---#
        self.location = ret.location
    
    def bandpass(self,center,width):
        '''Apply a two-pole Butterworth band-pass filter to the audio.
        
        Parameters
        ----------
        center : int, float
            The center frequency of the filter, in Hz.
        width : int, float
            The 3dB-point bandwidth of the filter, in Hz.'''
        #---Check inputs---#
        check_num(center,"center",0,self.sample_rate/2)
        check_num(width,"width",0.0000001)
        #---Find valid new file location---#
        npath = unused()
        #---Apply bandpass filter---#
        command = '"%s" "%s" "%s" bandpass %s %s'%(soxdir,self.location,npath,center,width)
        run_command(command)
        self.location = npath
    
    def bandreject(self,center,width):
        '''Apply a two-pole Butterworth band-reject filter to the audio.
        
        Parameters
        ----------
        center : int, float
            The center frequency of the filter, in Hz.
        width : int, float
            The 3dB-point bandwidth of the filter, in Hz.'''
        #---Check inputs---#
        check_num(center,"center",0,self.sample_rate/2)
        check_num(width,"width",0.0000001)
        #---Find valid new file location---#
        npath = unused()
        #---Apply bandreject filter---#
        command = '"%s" "%s" "%s" bandreject %s %s'%(soxdir,self.location,npath,center,width)
        run_command(command)
        self.location = npath
    
    def highpass(self,cutoff):
        '''Apply a two-pole Butterworth high-pass filter to the audio.
        
        Parameters
        ----------
        cutoff : int, float
            The filter's cutoff frequency, in Hz.'''
        #---Check inputs---#
        check_num(cutoff,"cutoff",0,self.sample_rate/2)
        #---Find valid new file location---#
        npath = unused()
        #---Apply highpass filter---#
        command = '"%s" "%s" "%s" highpass %s'%(soxdir,self.location,npath,cutoff)
        run_command(command)
        self.location = npath
    
    def lowpass(self,cutoff):
        '''Apply a two-pole Butterworth low-pass filter to the audio.
        
        Parameters
        ----------
        cutoff : int, float
            The filter's cutoff frequency, in Hz.'''
        #---Check and format inputs---#
        check_num(cutoff,"cutoff",0,self.sample_rate/2)
        #---Find valid new file location---#
        npath = unused()
        #---Apply lowpass filter---#
        command = '"%s" "%s" "%s" lowpass %s'%(soxdir,self.location,npath,cutoff)
        run_command(command)
        self.location = npath
    
    def repeat(self,count=2):
        '''Repeat the entire audio the specified number of times, trimming if
        necessary.
        
        Parameters
        ----------
        count : int, float, optional
            The number of times to repeat the audio.'''
        #---Check input count---#
        check_num(count,"count",0)
        #---Repeat audio---#
        ret = Sound("",self.sample_rate,self.channels,self.bits)
        for ii in xrange(int(math.ceil(count))):
            ret.open(self.location)
        self.location = ret.location
        if count > 0:
            self.crop(0,self.get_duration()*count/math.ceil(count))
    
    def reverse(self):
        '''Reverse the audio completely.'''
        #---Find valid new file location---#
        npath = unused()
        #---Reverse audio---#
        command = '"%s" "%s" "%s" reverse'%(soxdir,self.location,npath)
        run_command(command)
        self.location = npath
    
    def dcshift(self,shift):
        '''Apply a DC shift to the audio. This can be useful to remove a DC offset from
        the audio.
        
        Parameters
        ----------
        shift : int, float
            The amount by which to shift the audio, expressed as an amplitude (normal
            audio samples have an amplitude in the range +-1).'''
        #---Check input shift---#
        check_num(shift,"shift",-2,2)
        #---Find valid new file location---#
        npath = unused()
        #---Apply DC shift---#
        command = '"%s" "%s" "%s" dcshift %s 0.05'%(soxdir,self.location,npath,shift)
        run_command(command)
        self.location = npath
    
    def crop(self,start,end):
        '''Truncates the audio to the given interval.
        
        Parameters
        ----------
        start : int, float
            The start time for the desired interval, measured from the beginning of the
            audio, in seconds.
        end : int, float
            The end time for the desired interval, measured from the beginning of the
            audio, in seconds.'''
        #---Check inputs---#
        check_num(start,"start",0,self.get_duration())
        check_num(end,"end",start,self.get_duration())
        #---Find valid new file location---#
        npath = unused()
        #---Trim the audio---#
        command = '"%s" "%s" "%s" trim %s %s'%(soxdir,self.location,npath,start,end)
        run_command(command)
        self.location = npath
    
    def trim(self,start,end):
        '''Trims out the given interval of audio.
        
        Parameters
        ----------
        start : int, float
            The start time for the interval to remove, measured from the beginning of
            the audio, in seconds.
        end : int, float
            The end time for the interval to remove, measured from the beginning of
            the audio, in seconds.'''
        #---Check inputs---#
        check_num(start,"start",0,self.get_duration())
        check_num(end,"end",start,self.get_duration())
        #---Find valid new file location---#
        npath = unused()
        #---Trim the audio---#
        command = '"%s" "%s" "%s" trim 0 %s %s'%(soxdir,self.location,npath,start,end)
        run_command(command)
        self.location = npath
    
    def silence(self,trim_start=False,trim_end=False,duration=0.1,threshold=0):
        '''Removes silence from the beginning and/or end of the audio.
        
        Parameters
        ----------
        trim_start : bool, optional
            Whether or not to trim silence from the beginning of the audio.
        trim_end : bool, optional
            Whether or not to trim silence from the end of the audio.
        duration : int, float, optional
            The duration of non-silence required to count as a period of non-silence,
            in seconds.
        threshold : int, float, optional
            Amplitude threshold for non-silence.'''
        #---Check inputs---#
        check_bool(trim_start,"trim_start")
        check_bool(trim_end,"trim_end")
        check_num(duration,"duration",0)
        check_num(threshold,"threshold",0)
        #---Remove silence from beginning of audio---#
        if trim_start:
            #---Find valid new file location---#
            npath = unused()
            command = '"%s" "%s" "%s" silence 1 %s %s 0 0 0'%(soxdir,self.location,npath,duration,threshold)
            run_command(command)
            self.location = npath
        #---Remove silence from end of audio---#
        if trim_end:
            self.reverse()
            #---Find valid new file location---#
            npath = unused()
            command = '"%s" "%s" "%s" silence 1 %s %s 0 0 0'%(soxdir,self.location,npath,duration,threshold)
            run_command(command)
            self.location = npath
            self.reverse()
    
    def pad(self,length,position=0):
        '''Pad the audio with silence.
        
        Parameters
        ----------
        length : int, float
            The interval of silence to add to the audio, in seconds.
        position : int, float, optional
            The duration into the audio to place the interval of silence, in seconds.'''
        #---Check inputs---#
        check_num(length,"length",0)
        check_num(position,"position",0)
        #---Find valid new file location---#
        npath = unused()
        #---Pad the audio---#
        command = '"%s" "%s" "%s" pad %s@%s'%(soxdir,self.location,npath,length,position)
        run_command(command)
        self.location = npath
    
    def delay(self,length,channel=None):
        '''Delay one or more audio channels.
        
        Parameters
        ----------
        length : int, float
            The amount of time to delay the audio (in seconds).
        channel : None, int, optional
            If channel is None, delay all channels equally. If channel is an integer,
            apply delay only to the specified channel.'''
        #---Check and process input length---#
        check_num(length,"length",0)
        if channel == None:
            length = [length]*self.channels
        else:
            check_num(channel,"channel",1,self.channels)
            length = [0]*(channel-1) + [length] + [0]*(self.channels-channel)
        length = " " + str(length)[1:-1].replace(",","")
        #---Find valid new file location---#
        npath = unused()
        #---Apply delay---#
        command = '"%s" "%s" "%s" delay %s'%(soxdir,self.location,npath,length)
        run_command(command)
        self.location = npath
    
    def noisered(self,amount=0.5):
        '''Reduce noise in the audio signal by profiling and filtering. This
        effect is moderatly effective at removing consistent background
        noise such as hiss or hum.
        
        Parameters
        ----------
        amount : int, float, optional
            Specifies a proportion of noise to be removed (between 0 and 1). Higher
            values will remove more noise but present a greater likelihood of removing
            wanted components of the audio signal.'''
        #---Check input amount---#
        check_num(amount,"amount",0,1)
        #---Find valid temporary file location---#
        npath = unused()
        #---Calculate noise profile---#
        command = '"%s" "%s" -n noiseprof "%s"'%(soxdir,self.location,npath)
        run_command(command)
        #---Find valid new file location---#
        npath2 = unused()
        #---Reduce noise---#
        command = '"%s" "%s" "%s" noisered "%s" %s'%(soxdir,self.location,npath2,npath,amount)
        run_command(command)
        self.location = npath2
    
    def vad(self,trim_start=False,trim_end=False,duration=0.25,threshold=7):
        '''Voice Activity Detector. Attempts to remove silence and quiet background
        sounds from the beginning and/or end of recordings of speech.
        
        Parameters
        ----------
        trim_start : bool, optional
            Whether or not to trim silence from the beginning of the audio.
        trim_end : bool, optional
            Whether or not to trim silence from the end of the audio.
        duration : int, float, optional
            Allowed gap (in seconds) between quieter/shorter bursts of audio to include
            prior to the detected trigger point.
        threshold : int, optional
            The measurement level used to trigger activity detection.'''
        #---Check and process inputs---#
        check_bool(trim_start,"trim_start")
        check_bool(trim_end,"trim_end")
        check_num(threshold,"threshold",0,20)
        check_num(duration,"duration",0.1,1)
        #---Apply VAD to start of audio---#
        if trim_start:
            #---Find valid new file location---#
            npath = unused()
            command = '"%s" "%s" "%s" vad -t %s -g %s'%(soxdir,self.location,npath,threshold,duration)
            run_command(command)
            self.location = npath
        #---Apply VAD to end of audio---#
        if trim_end:
            self.reverse()
            #---Find valid new file location---#
            npath = unused()
            command = '"%s" "%s" "%s" vad -t %s -g %s'%(soxdir,self.location,npath,threshold,duration)
            run_command(command)
            self.location = npath
            self.reverse()
    
    def deemph(self):
        '''Apply Compact Disc (IEC 60908) de-emphasis (a treble attenuation shelving
        filter).'''
        #---Find valid new file location---#
        npath = unused()
        #---Apply CD de-emphasis filter---#
        command = '"%s" "%s" "%s" deemph'%(soxdir,self.location,npath)
        run_command(command)
        self.location = npath
    
    def earwax(self):
        '''Makes audio easier to listen to on headphones.'''
        #---find valid new file location---#
        npath = unused()
        #---Apply earwax effect---#
        command = '"%s" "%s" "%s" earwax'%(soxdir,self.location,npath)
        run_command(command)
        self.location = npath
    
    def karaoke(self):
        '''Out Of Phase Stereo effect. Mixes stereo to twin-mono where each mono
        channel contains the difference between the left and right stereo channels.
        This is sometimes known as the 'karaoke' effect as it often has the effect of
        removing most or all of the vocals from a recording.'''
        #---Ensure sufficient number of channels available---#
        if self.channels < 2:
            raise RuntimeError("Sound has insufficient channels to apply OOPS effect.")
        #---Find valid new file location---#
        npath = unused()
        #---Apply OOPS (karaoke) effect---#
        command = '"%s" "%s" "%s" oops'%(soxdir,self.location,npath)
        run_command(command)
        self.location = npath
    
    def riaa(self):
        '''Apply RIAA vinyl playback equalization. The sampling rate must be one of:
        44.1, 48, 88.2, 96 kHz.'''
        #---Ensure valid sample_rate---#
        sample_rate = int(self.sample_rate)
        if sample_rate not in (44100,48000,88200,96000):
            raise RuntimeError("The sampling rate must be one of: 44.1, 48, 88.2, 96 kHz.")
        #---Find valid new file location---#
        npath = unused()
        #---Apply RIAA playback equalization---#
        command = '"%s" "%s" "%s" riaa'%(soxdir,self.location,npath)
        run_command(command)
        self.location = npath
    
    def stats(self):
        '''Returns time domain statistical information about the audio.
        
        Returns
        -------
        out : dict
            A dictionary with the following entries:
                'max_amp': The maximum sample value in the audio.
                'min_amp': The minimum sample value in the audio.
                'mid_amp': The average of 'max_amp' and 'min_amp'.
                'mean_amp': The average value of all samples in the audio.
                'mean_norm': The average of the absolute value of all samples in the audio.
                'rms_amp': The average root-mean-square value of all samples in the audio.
                'max_delta': The maximum change between two consecutive sample values.
                'min_delta': The minimum change between two consecutive sample values.
                'mean_delta': The average change between any two consecutive sample values.
                'rms_delta': The average root-mean-sqaure value of all two consecutive
                             sample values.'''
        #---Compute unformatted 'stat' statistics---#
        command = '"%s" "%s" -n stat'%(soxdir,self.location)
        try:
            stats = subprocess.Popen(command,shell=True,stderr=subprocess.PIPE)
        except:
            stats = subprocess.Popen(shlex.split(command),shell=True,stderr=subprocess.PIPE)
        stats = stats.communicate()[1]
        #---Format statistics---#
        ret = {}
        ret['max_amp'] = float(stats.split("Maximum amplitude:")[1].split("\n")[0].split("\r")[0].replace(" ",""))
        ret['min_amp'] = float(stats.split("Minimum amplitude:")[1].split("\n")[0].split("\r")[0].replace(" ",""))
        ret['mid_amp'] = float(stats.split("Midline amplitude:")[1].split("\n")[0].split("\r")[0].replace(" ",""))
        ret['mean_norm'] = float(stats.split("Mean    norm:")[1].split("\n")[0].split("\r")[0].replace(" ",""))
        ret['mean_amp'] = float(stats.split("Mean    amplitude:")[1].split("\n")[0].split("\r")[0].replace(" ",""))
        ret['rms_amp'] = float(stats.split("RMS     amplitude:")[1].split("\n")[0].split("\r")[0].replace(" ",""))
        ret['max_delta'] = float(stats.split("Maximum delta:")[1].split("\n")[0].split("\r")[0].replace(" ",""))
        ret['min_delta'] = float(stats.split("Minimum delta:")[1].split("\n")[0].split("\r")[0].replace(" ",""))
        ret['mean_delta'] = float(stats.split("Mean    delta:")[1].split("\n")[0].split("\r")[0].replace(" ",""))
        ret['rms_delta'] = float(stats.split("RMS     delta:")[1].split("\n")[0].split("\r")[0].replace(" ",""))
        return ret
    
    def delete_channel(self,channel):
        '''Delete a channel from the audio.
        
        Parameters
        ----------
        channel : int
            The channel to be deleted.'''
        #---Check and process inputs---#
        check_num(channel,"channel",1,self.channels+1)
        channels = range(1,channel) + range(channel+1,self.channels+1)
        exxs = ""
        for ii in channels:
            exxs += " "+str(ii)
        #---Find valid new file location---#
        npath = unused()
        #---Delete channel---#
        command = '"%s" "%s" "%s" remix%s'%(soxdir,self.location,npath,exxs)
        run_command(command)
        self.location = npath
        self.channels -= 1
    
    def add_channel(self,channel=None):
        '''Add a blank channel to the audio.
        
        Parameters
        ----------
        channel : None, int, optional
            The channel to be added. If channel is None, adds the new channel at the
            end of the existing channels. If an integer is given, inserts the new
            channel at that index, and shifts all the higher-numbered channels
            accordingly.'''
        #---Check and process inputs---#
        exxs = ""
        if channel == None:
            for ii in xrange(1,self.channels+1):
                exxs += " "+str(ii)
            exxs += " 1v0"
        else:
            for ii in xrange(1,channel):
                exxs += " "+str(ii)
            exxs += " 1v0"
            for ii in xrange(channel,self.channels+1):
                exxs += " "+str(ii)
        #---Find valid new file location---#
        npath = unused()
        #---Add channel---#
        command = '"%s" "%s" "%s" remix%s'%(soxdir,self.location,npath,exxs)
        run_command(command)
        self.location = npath
        self.channels += 1
    
    def remix_channel(self,channel=None,remix=[]):
        '''Writes to a channel as a combination of other channels.
        
        Parameters
        ----------
        channel : None, int, optional
            The channel to be written to. If channel is None, adds and writes to a new
            channel at the end of the existing channels. If an integer is given,
            writes to the channel at that index.
        remix : list, tuple, optional
            List of the component channel indices to be remixed to the specified
            channel.'''
        #---Check and process inputs---#
        remixx = ""
        for ii in remix:
            remixx += ","+str(int(ii))
        exxs = ""
        if channel == None:
            for ii in xrange(1,self.channels+1):
                exxs += " "+str(ii)
            exxs += " "+remixx[1:]
        else:
            for ii in xrange(1,channel):
                exxs += " "+str(ii)
            exxs += " "+remixx[1:]
            for ii in xrange(channel,self.channels+1):
                exxs += " "+str(ii)
        #---Find valid new file location---#
        npath = unused()
        #---Write to channel---#
        command = '"%s" "%s" "%s" remix%s'%(soxdir,self.location,npath,exxs)
        run_command(command)
        self.location = npath
        if channel == None:
            self.channels += 1
    
    def spectrogram(self,path,width=800,height=400,title=None,comment=None,window="hann",colors=249,min_dB=-120,max_dB=0,printer_friendly=False,monochrome=False):
        '''Create a spectrogram of the audio, and save to a .png image file.
        
        Parameters
        ----------
        path : str
            Desired path to save the spectrogram. The path should specify a .png file
            in a valid directory.
        width : int, optional
            Width of the output spectrogram, in pixels.
        height : int, optional
            Height of the output spectrogram, in pixels.
        title : None, str, optional
            The image title--i.e. text to display above the spectrogram. If None, no
            title will be displayed.
        comment : None, str, optional
            Optional image comment--i.e. text to display below and to the left of the
            spectrogram. If None, no comment will be displayed.
        window : str, optional
            The name of the window function to use for the Discrete Fourier Transform
            (DFT) calculation. Options are: "hann","hamming","bartlett","rectangular",
            and "kaiser".
        colors : int, optional
            Number of distinct color bands to use in the spectrogram.
        min_dB : int, float, optional
            Lower end of the dynamic range of the spectrogram.
        max_dB : int, float, optional
            Upper end of the dynamic range of the spectrogram.
        printer_friendly : bool, optional
            If True, uses a light background color.
        monochrome : bool, optional
            If True, spectrogram will be rendered in grayscale.'''
        #---Ensure that 'spectrogram' is included with this computer's distribution of SoX---#
        try:
            output = subprocess.check_output('"%s" --help'%(soxdir),shell=True)
        except:
            output = subprocess.check_output(shlex.split('"%s" --help'%(soxdir)),shell=True)
        if "spectrogram" not in output:
            raise RuntimeError("'spectrogram' effect not included with this computer's installation of SoX.")
        #---Check and format inputs---#
        check_str(window,"window",["hann","hamming","bartlett","rectangular","kaiser"])
        check_bool(monochrome,"monochrome")
        check_num(colors,"colors",1,249)
        colors = int(colors)
        check_bool(printer_friendly,"printer_friendly")
        check_num(width,"width",100,5000)
        check_num(height,"height",150,2400)
        check_num(min_dB,"min_dB")
        check_num(max_dB,"max_dB",-100,100)
        if max_dB-min_dB < 20 or max_dB-min_dB > 180:
            raise ValueError("`max_dB' and `min_dB' must not differ by more than 180 or less than 20.")
        if title == None:
            exxs = ""
        else:
            exxs = ' -t "%s"'%str(title)
        if comment != None:
            exxs += ' -c "%s"'%str(comment)
        if printer_friendly:
            exxs += " -l"
        if monochrome:
            exxs += " -m"
        exxs += " -q %s"%colors
        exxs += " -w %s"%window.capitalize()
        #---Check and process input path---#
        path = reduce_path(path,folder=True)
        if path[-4:] != ".png":
            raise RuntimeError("`path' must specify a .png file.")
        #---Normalize height and width---#
        adj_width = int(width/self.get_duration())
        height = int(height/self.channels)
        #---Produce and save spectrogram---#
        command = '"%s" "%s" -n spectrogram -x %s -X %s -y %s -z %s -Z %s%s -o "%s"'%(soxdir,self.location,width,adj_width,height,max_dB-min_dB,max_dB,exxs,path)
        run_command(command)
    
    def tremolo(self,speed,tremolo=40):
        '''Apply a tremolo (low frequency amplitude modulation) effect to the audio.
        
        Parameters
        ----------
        speed : int, float
            The tremolo modulation frequency, in Hz.
        tremolo : int, float, optional
            The amount by which to affect the pitch, measured in cents (100 cents = 1
            semitone).'''
        #---Check inputs---#
        check_num(speed,"speed",0)
        check_num(tremolo,"tremolo",0)
        #---Find valid new file location---#
        npath = unused()
        #---Apply tremolo effect---#
        command = '"%s" "%s" "%s" tremolo %s %s'%(soxdir,self.location,npath,speed,tremolo)
        run_command(command)
        self.location = npath
    
    def bend(self,position,bend,duration):
        '''Change pitch by the specified amount for the specified interval.
        
        Parameters
        ----------
        position : int, float
            The time to begin the bend effect, measured in seconds from the start of
            the audio.
        bend : int, float
            The amount by which to bend the pitch, measured in cents (100 cents = 1
            semitone), and can be either positive or negative.
        duration : int, float
            The length of the interval of time over which the pitch will be bent.'''
        #---Check and reformat inputs---#
        check_num(position,"position",0,self.get_duration())
        check_num(bend,"bend")
        check_num(duration,"duration",0,self.get_duration()-position)
        #---Find valid new file location---#
        npath = unused()
        #---Apply bend effect---#
        command = '"%s" "%s" "%s" bend %s,%s,%s'%(soxdir,self.location,npath,position,bend,duration)
        run_command(command)
        self.location = npath
    
    def bass(self,gain,center,width):
        '''Boost or cut the bass frequencies of the audio using a two-pole shelving
        filter. This is also known as shelving equalization.

        Parameters
        ----------
        gain : int, float
            The gain at 0 Hz, in decibels.
        center : int, float
            The center frequency for the applied amplification, in Hz.
        width : int, float
            The bandwidth of the applied amplification, in Hz.'''
        #---Check inputs---#
        check_num(gain,"gain",-50,50)
        check_num(center,"center",0,self.sample_rate/2)
        check_num(width,"width",0.0000001)
        #---Find valid new file location---#
        npath = unused()
        #---Apply bass boost effect---#
        command ='"%s" "%s" "%s" bass %s %s %sh'%(soxdir,self.location,npath,gain,center,width)
        run_command(command)
        self.location = npath
    
    def treble(self,gain,center,width):
        '''Boost or cut the treble frequencies of the audio using a two-pole shelving
        filter. This is also known as shelving equalization.

        Parameters
        ----------
        gain : int, float
            The gain at the lower of 22 kHz and the Nyquist frequency, in decibels.
        center : int, float
            The center frequency for the applied amplification, in Hz.
        width : int, float
            The bandwidth of the applied amplification, in Hz.'''
        #---Check inputs---#
        check_num(gain,"gain",-50,50)
        check_num(center,"center",0,self.sample_rate/2)
        check_num(width,"width",0.0000001)
        #---Find valid new file location---#
        npath = unused()
        #---Apply treble boost effect---#
        command = '"%s" "%s" "%s" treble %s %s %sh'%(soxdir,self.location,npath,gain,center,width)
        run_command(command)
        self.location = npath
    
    def equalizer(self,gain,center,width):
        '''Apply a two-pole peaking equalization filter. With this filter, the signal-
        level at and around a selected frequency can be increased or decreased, whilst
        (unlike bandpass and bandreject filters) that at all other frequencies is
        unchanged.
        
        Parameters
        ----------
        gain : int, float
            The gain at the center frequency, in decibels.
        center : int, float
            The center frequency for the applied amplification, in Hz.
        width : int, float
            The bandwidth of the applied amplification, in Hz.'''
        #---Check inputs---#
        check_num(gain,"gain")
        check_num(center,"center")
        check_num(width,"width")
        #---Find valid new file location---#
        npath = unused()
        #---Apply equalizer filter---#
        command = '"%s" "%s" "%s" equalizer %s %sh %s'%(soxdir,self.location,npath,center,width,gain)
        run_command(command)
        self.location = npath
    
    def pitch(self,shift):
        '''Change the audio pitch (but not tempo).

        Parameters
        ----------
        shift : int, float
            The number of cents (100 cents  = 1 semitone) to shift the audio pitch.
            Shift can be either positive or negative.'''
        #---Check and process inputs---#
        check_num(shift,"shift")
        #---Find valid new file location---#
        npath = unused()
        #---Change audio pitch---#
        command = '"%s" "%s" "%s" pitch %s'%(soxdir,self.location,npath,shift)
        run_command(command)
        self.location = npath
    
    def speed(self,factor=1,cents=0):
        '''Adjust the audio speed (pitch and tempo together). You can control this with
        either the `factor' or `cents' parameter.
        
        Parameters
        ----------
        factor : int, float, optional
            Setting factor will adjust the audio speed by a fixed ratio: greater than 1
            speeds it up, less than 1 slows it down.
        cents : int, float, optional
            Setting cents will adjust the pitch (and tempo) by a fixed number of
            cents (100 cents = 1 semitone): greater than 0 increases it, less than 0
            decreases it.'''
        #---Determine which input type is being used---#
        if factor != 1 and cents != 0:
            raise ValueError("You may only specify either `factor' or `cents', not both.")
        #---Find valid new file location---#
        npath = unused()
        #Specified by `factor':
        if factor != 1:
            #---Check input factor---#
            check_num(factor,"factor",0)
            #---Determine proper command syntax---#
            command = '"%s" "%s" "%s" speed %s'%(soxdir,self.location,npath,factor)
        #Specified by `cents':
        elif cents != 0:
            #---Check input cents---#
            check_num(cents,"cents")
            #---Determine proper command syntax---#
            command = '"%s" "%s" "%s" speed %sc'%(soxdir,self.location,npath,cents)
        #---Adjust audio speed---#
        run_command(command)
        self.location = npath
    
    def tempo(self,factor):
        '''Change the audio playback speed but not its pitch. This effect uses the
        WSOLA algorithm.

        factor : int, float
            Sets the fixed ratio of the new speed to the old speed: greater than 1
            speeds it up, less than 1 slows it down.'''
        #---Check and format inputs---#
        check_num(factor,"factor",0)
        #---Find valid new file location---#
        npath = unused()
        #---Adjust audio tempo---#
        command = '"%s" "%s" "%s" tempo %s'%(soxdir,self.location,npath,factor)
        run_command(command)
        self.location = npath
    
    def chorus(self,delay,decay):
        '''Add a chorus effect to the audio. This can make a single vocal sound like a
        chorus, but can also be applied to instrumentation.
        
        The chorus effect resembles the echo effect with a short delay, but whereas
        echo has a constant delay, with chorus, the delay is varied using sinusoidal 
        modulation. The delayed sound will sound slower or faster, that is the delayed
        sound tuned around the original one, like in a chorus where some vocals are
        slightly off key.
        
        Parameters
        ----------
        delay : int, float
            The average time difference between the original signal and the echo, in
            milliseconds.
        decay : int, float
            The gain applied between the echo and the original signal, expressed as a
            ratio.'''
        #---Check and process inputs---#
        check_num(delay,"delay")
        check_num(decay,"decay",0,1)
        #---Find valid new file location---#
        npath = unused()
        #---Apply chorus effect---#
        command = '"%s" "%s" "%s" chorus 1 1 %s %s .25 2 -s'%(soxdir,self.location,npath,delay,decay)
        run_command(command)
        self.location = npath
    
    def echo(self,delay,decay):
        '''Add echoing to the audio. Echoes are reflected sound and can occur naturally
        amongst mountains (and sometimes large buildings) when talking or shouting;
        digital echo effects emulate this behaviour and are often used to help fill out
        the sound of a single instrument or vocal.
        
        Parameters
        ----------
        delay : int, float
            The time difference between the original signal and the echo, in
            milliseconds.
        decay : int, float
            The gain applied between the echo and the original signal, expressed as a
            ratio.'''
        #---Check and process inputs---#
        check_num(delay,"delay")
        check_num(decay,"decay",0,1)
        #---Find valid new file location---#
        npath = unused()
        #---Apply echo effect---#
        command = '"%s" "%s" "%s" echo 1 1 %s %s'%(soxdir,self.location,npath,delay,decay)
        run_command(command)
        self.location = npath
    
    def reverb(self,reverb=50,softness=50,room_scale=100,stereo_depth=100,delay=0):
        '''Add reverberation to the audio using the 'freeverb' algorithm.
        
        Parameters
        ----------
        reverb : int, float, optional
            Amount of reverberation, as a percentage of the original audio.
        softness : int, float, optional
             High-frequency damping, as a percentage. This determines how 'hard' the
             reverberation sounds, high values correspond to soft surfaces, low values
             correspond to hard surfaces.
        room_scale : int, float, optional
            Size of the simulated room (as a percentage).
        stereo_depth : int, float, optional
            Stereo-depth, as a percentage.
        delay : int, float, optional
            Delay before first reverberation, in milliseconds.'''
        #---Check and format inputs---#
        check_num(reverb,"reverb",0,100)
        check_num(softness,"softness",0,100)
        check_num(room_scale,"room_scale",0,100)
        check_num(stereo_depth,"stereo_depth",0,100)
        check_num(delay,"delay",0,500)
        #---Find valid new file location---#
        npath = unused()
        #---Apply reverb effect---#
        command = '"%s" "%s" "%s" reverb %s %s %s %s %s'%(soxdir,self.location,npath,reverb,softness,room_scale,stereo_depth,delay)
        run_command(command)
        self.location = npath
    
    def gain(self,gain):
        '''Apply an amplification or attenuation to the audio signal.
        
        Parameters
        ----------
        gain : int, float
            The gain to apply to the audio, in decibels.'''
        #---Check inputs---#
        check_num(gain,"gain",-50,50)
        #---Find valid new file location---#
        npath = unused()
        #---Apply gain---#
        command = '"%s" "%s" "%s" gain %s'%(soxdir,self.location,npath,gain)
        run_command(command)
        self.location = npath
    
    def loudness(self,gain):
        '''Loudness control - similar to Sound.gain, but provides equalization
        for the human auditory system.
        
        Parameters
        ----------
        gain : int, float
            The gain to apply to the audio, in decibels.'''
        #---Check inputs---#
        check_num(gain,"gain",-50,15)
        #---Find valid new file location---#
        npath = unused()
        #---Apply loudness adjustment---#
        command = '"%s" "%s" "%s" loudness %s'%(soxdir,self.location,npath,gain)
        run_command(command)
        self.location = npath
    
    def norm(self,dB_level):
        '''Normalize the audio to the given dB-level.
        
        Parameters
        ----------
        dB_level : int, float
            Level to normalize the audio to, in decibels.'''
        #---Check input dB_level---#
        check_num(dB_level,"dB_level",-50,50)
        #---Find valid new file location---#
        npath = unused()
        #---Normalize the audio---#
        command = '"%s" "%s" "%s" norm %s'%(soxdir,self.location,npath,dB_level)
        run_command(command)
        self.location = npath
        
    def amplitude_gain(self,gain):
        '''Apply an amplification or an attenuation to the audio signal.

        Parameters
        ----------
        gain : int, float
            Gain to apply to the audio, as a ratio of the new amplitudes to the old
            amplitudes. Positive values less than 1 will attenuate the audio, and
            values greater than 1 will amplify it. Negative values will invert the
            audio before applying the gain.'''
        #---Check inputs---#
        check_num(gain,"gain")
        #---Find valid new file location---#
        npath = unused()
        #---Adjust volume---#
        command = '"%s" "%s" "%s" vol %s amplitude'%(soxdir,self.location,npath,gain)
        run_command(command)
        self.location = npath
    
    def power_gain(self,gain):
        '''Apply an amplification or an attenuation to the audio signal.

        Parameters
        ----------
        gain : int, float
            Gain to apply to the audio, as a ratio of the new power to the old
            power. Positive values less than 1 will attenuate the audio, and
            values greater than 1 will amplify it. Negative values will invert the
            audio before applying the gain.'''
        #---Check inputs---#
        check_num(gain,"gain")
        #---Find valid new file location---#
        npath = unused()
        #---Adjust volume---#
        command = '"%s" "%s" "%s" vol %s power'%(soxdir,self.location,npath,gain)
        run_command(command)
        self.location = npath
    
    def fade(self,fade_in_length=0,fade_out_length=0,type="logarithmic"):
        '''Apply a fade effect to the beginning and/or end of the audio.
        
        Parameters
        ----------
        fade_in_length : int, float, optional
            The duration of the fade-in effect, in seconds.
        fade_out_length : int, float, optional
            The duration of the fade-out effect, in seconds.
        type : str, optional
            The shape of the fade curve. Options are: "quarter", "half", "linear",
            "logarithmic", "parabolic".'''
        #---Check and format inputs---#
        check_num(fade_in_length,"fade_in_length",0)
        check_num(fade_out_length,"fade_out_length",0)
        check_str(type,"type",["quarter","half","linear","logarithmic","parabolic"])
        type = {"quarter":"q","half":"h","linear":"t","logarithmic":"l","parabolic":"p"}[type]
        #---Find valid new file location---#
        npath = unused()
        #---Apply fade effect---#
        command = '"%s" "%s" "%s" fade %s %s'%(soxdir,self.location,npath,type,fade_in_length)
        run_command(command)
        self.location = npath
    
    def dither(self,selective=False,filter="white"):
        '''Apply dithering to the audio. Dithering delibrately adds a small amount of
        noise to the signal in order to mask audible quantization effects that can
        occur if the output sample size is less than 24 bits.

        Parameters
        ----------
        selective : bool, optional
            If True, method will automatically attempt to only apply dithering where it
            is needed.
        filter: str, optional
            Specifies the particular noise-shaping filter that will be used. Options
            are: "white", "sloped", "shaped", "lipshitz", "f-weighted",
            "modified-e-weighted", "improved-e-weighted", "gesemann", "shibata",
            "low-shibata", and "high-shibata".'''
        #---Check and format inputs---#
        check_bool(selective,"selective")
        check_str(filter,"filter",["white","sloped","shaped","lipshitz","f-weighted","modified-e-weighted","improved-e-weighted","gesemann","shibata","low-shibata","high-shibata"])
        selective = {True:" -a",False:""}[selective]
        filter = {"white":"","sloped":" -S","shaped":" -s","lipshitz":" -f lipshitz","f-weighted":" -f f-weighted","modified-e-weighted":" -f modified-e-weighted","improved-e-weighted":" -f improved-e-weighted","gesemann":" -f gesemann","shibata":" -f shibata","low-shibata":" -f low-shibata","high-shibata":" -f high-shibata"}[filter]
        #---Find valid new file location---#
        npath = unused()
        #---Apply dithering---#
        command = '"%s" "%s" "%s" dither%s%s'%(soxdir,self.location,npath,filter,selective)
        run_command(command)
        self.location = npath
    
    def contrast(self,amount):
        '''Modifies an audio signal to make it sound louder.
        
        Parameters
        ----------
        amount : int, float
            The amount of enhancement, as a number in the range 0-100. Note that amount
            = 0 still gives a significant contrast enhancement.'''
        #---Check input amount---#
        check_num(amount,"amount",0,100)
        #---Find valid new file location---#
        npath = unused()
        #---Apply contrast---#
        command = '"%s" "%s" "%s" contrast %s'%(soxdir,self.location,npath,amount)
        run_command(command)
        self.location = npath
    
    def hilbert(self,taps=None):
        '''Apply an odd-tap Hilbert transform filter, phase shifting the signal by 90
        degrees.

        This is used in many matrix coding schemes and for analytic signal generation.
        The process is often written as a multiplication by `i' or `j', the imaginary
        unit.
        
        Parameters
        ----------
        taps : None, int, optional
            Number of taps used in calculating the transform, and must be an odd
            number. More taps correspond to a more ideal filter, with fewer edge
            effects at high and low frequencies. If taps is None, a taps value will be
            chosen so that attenuation at low frequencies is limited to below 75 Hz.'''
        #---Find valid new file location---#
        npath = unused()
        #---Apply Hilbert transform---#
        if taps == None:
            command = '"%s" "%s" "%s" hilbert'%(soxdir,self.location,npath)
        else:
            check_num(taps,"taps")
            command = '"%s" "%s" "%s" hilbert -n %s'%(soxdir,self.location,npath,taps)
        run_command(command)
        self.location = npath

def record(path,duration,sample_rate=44100,channels=2,bits=16):
    '''Record a sample of audio from the system default audio input and save the
    result as an audio file.
    
    Parameters
    ----------
    path : str
        Desired path to save the Sound object's audio. The path should specify a
        file in a valid directory.
    duration : int, float
        The desired length of time (in seconds) to record audio.
    sample_rate : int, optional
        The desired sample rate for the audio, in samples per second (Hz). Common 
        sample rates are: 32000, 44100 (CD), 48000, 88200, 96000, and 192000 Hz.
    channels : int, optional
        The number of channels used in the audio. One (mono) and two (stereo) are 
        widely used. Surround sound audio typically contains six or more channels.
    bits : int, optional
        The number of bits used to store each sample. Today, 16-bit is commonly
        used. 8-bit was popular in the early days of computer audio. 24-bit is used
        in the professional audio arena. Other sizes are also used.'''
    path = reduce_path(path,folder=True)
    command = '"%s" -d -r %s -c %s -b %s "%s" trim 0 %s:%s' % (soxdir,sample_rate,channels,bits,path,int(duration/60),int(duration%60))
    run_command(command)

def play(path):
    '''Play the specified audio to the system default audio player.
    
    Parameters
    ----------
    path : str, Sound
        Either the path to an existing audio file, or an instance of a Sound object.'''
    if isinstance(path,Sound):
        path.play()
    else:
        temp = Sound()
        temp.open(path)
        temp.play()

def info(path):
    '''Retrieves various audio file metadata.
    
    Parameters
    ----------
    path : str, Sound
        Either the path to an existing audio file, or an instance of a Sound object.
    
    Returns
    -------
    out : dict
        A dictionary with the following keys:
            'file': The absolute path to the sound file.
            'channels': The number of channels used in the audio.
            'sample_rate': The sample rate for the audio, in samples per second (Hz).
            'bits': The number of bits used to store each sample.
            'duration': The duration of the audio, in seconds.
            'samples': The number of total samples in the audio file.
            'file_size': The size of the audio file, in bytes.
            'bit_rate': The bitrate, averaged over the whole file.
            'encoding': The name of the audio encoding.'''
    if isinstance(path,Sound):
        path = path.location
    path = reduce_path(path)
    command = '"%s" --i "%s"'%(soxdir,path)
    try:
        info = subprocess.check_output(command,shell=True)
    except:
        info = subprocess.check_output(shlex.split(command),shell=True)
    ret = {}
    ret['file'] = path
    ret['channels'] = int(info.split("Channels")[1].split(": ")[1].split("\r")[0].split("\n")[0])
    ret['sample_rate'] = int(info.split("Sample Rate")[1].split(": ")[1].split("\r")[0].split("\n")[0])
    ret['bits'] = int(info.split("Precision")[1].split(": ")[1].split("-bit")[0])
    try:
        ret['duration'] = info.split("Duration")[1].split(" : ")[1].split("\r")[0].split("\n")[0]
        ret['duration'] = int(ret['duration'].split(":")[0])*3600 + int(ret['duration'].split(":")[1])*60 + float(ret['duration'].split(":")[2].split(" = ")[0])
        ret['samples'] = int(info.split(" = ")[1].split(" samples ")[0].replace(" samples",""))
    except IndexError:
        ret['duration'] = 0.0
        ret['samples'] = 0
    ret['file_size'] = int(os.path.getsize(path))
    ret['bit_rate'] = int(ret['channels']*ret['sample_rate']*ret['precision'])
    ret['bit_rate'] = int(round(ret['bit_rate'],2-int(math.floor(math.log10(ret['bit_rate'])))))
    ret['encoding'] = info.split("Sample Encoding: ")[1].split("\r")[0].split("\n")[0]
    return ret

def unused():
    '''Helper function: finds an unused audio file name in the temp folder'''
    npath = "/opn"
    nnum = 0
    while len(npath) == 4:
        if not os.path.isfile(tmpdir+npath+str(nnum)+".wav"):
            npath = tmpdir+npath+str(nnum)+".wav"
        nnum += 1
    npath = reduce_path(npath)
    return npath

def run_command(command,wait=True):
    '''Helper function: runs the given command'''
    if wait:
        try:
            subprocess.check_call(command,shell=True)
        except:
            subprocess.check_call(shlex.split(command),shell=True)
    else:
        try:
            subprocess.Popen(command,shell=True)
        except:
            subprocess.Popen(shlex.split(command),shell=True)

def reduce_path(path,file=False,folder=False):
    '''Helper function: converts given path into a standardized form'''
    if not isinstance(path,str):
        raise TypeError("`path' must be a string.")
    path = path.replace("\\","/")
    path = os.path.abspath(os.path.expandvars(os.path.expanduser(path)))
    if file:
        if not os.path.isfile(path):
            raise RuntimeError("Path parameter must specify a valid audio file")
    if folder:
        if not os.path.isdir(path.replace(path.split("/")[-1],"")):
            raise RuntimeError("`path' must be in valid directory.")
    return path

def check_num(value,name,min=None,max=None,integer=False,listed=False):
    name = "`" + name + "'"
    if listed:
        name = "All " + name + " values"
    if integer:
        if not isinstance(value,int):
            raise TypeError("%s must be an integer."%name)
    if not isinstance(value,(int,float,long)):
        raise TypeError("%s must be an integer or floating point number."%name)
    if max != None and min != None and (value < min or value > max):
        raise ValueError("%s must be in the range %s--%s."%(name,min,max))
    elif min != None and value < min:
        raise ValueError("%s must be at least %s."%(name,min))
    elif max != None and value > max:
        raise ValueError("%s must be at most %s."%(name,max))

def check_bool(value,name):
    if not isinstance(value,bool):
        raise TypeError("`%s' must be either True or False."%name)

def check_str(value,name,values=None,listed=False):
    if listed:
        if not isinstance(value,basestring):
            raise TypeError("All `%s' values must be strings."%name)
    else:
        if not isinstance(value,basestring):
            raise TypeError("`%s' must be a string."%name)
    if values != None:
        if value not in values:
            raise ValueError("'%s' not a recognized value for `%s'."%(value,name))